package com.lc1993929.shucao.service;

import com.lc1993929.shucao.constants.Constants;
import com.lc1993929.shucao.entity.ClientRes;
import com.lc1993929.shucao.entity.Player;
import com.lc1993929.shucao.entity.PlayerMap;
import com.lc1993929.shucao.enums.CanNotInEnum;
import com.lc1993929.shucao.enums.Direct;
import com.lc1993929.shucao.enums.NameEnum;
import com.lc1993929.shucao.enums.MapTypeEnum;
import com.lc1993929.shucao.map.Coord;
import com.lc1993929.shucao.util.AStar;
import com.lc1993929.shucao.util.AStar2;
import com.lc1993929.shucao.util.MapUtil;
import com.lc1993929.shucao.util.Util;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class HeiMaService {


    public void start(PlayerMap playerMap) {
        Player player1 = new Player();
        Player player2 = new Player();
        Player player3 = new Player();
        Player player4 = new Player();


        if (NameEnum.SHU.getValue().equals(playerMap.getName())) {
            player1.setName(MapTypeEnum.shu1.getValue());
            player2.setName(MapTypeEnum.shu2.getValue());
            player3.setName(MapTypeEnum.shu3.getValue());
            player4.setName(MapTypeEnum.shu4.getValue());
        } else {
            player1.setName(MapTypeEnum.cao1.getValue());
            player2.setName(MapTypeEnum.cao2.getValue());
            player3.setName(MapTypeEnum.cao3.getValue());
            player4.setName(MapTypeEnum.cao4.getValue());
        }

        Coord player1Coord = MapUtil.findCoord(playerMap.getMap(), player1.getName());
        player1.setCoord(player1Coord);
        Coord player2Coord = MapUtil.findCoord(playerMap.getMap(), player2.getName());
        player2.setCoord(player2Coord);
        Coord player3Coord = MapUtil.findCoord(playerMap.getMap(), player3.getName());
        player3.setCoord(player3Coord);
        Coord player4Coord = MapUtil.findCoord(playerMap.getMap(), player4.getName());
        player4.setCoord(player4Coord);

        Coord targetCoord = MapUtil.findCoord(playerMap.getMap(), MapTypeEnum.target.getValue());
        Constants.targetCoord = targetCoord;


        if (NameEnum.SHU.getValue().equals(playerMap.getName())) {
            if (MapTypeEnum.shu1.getValue().equals(playerMap.getTargetWith())) {
                player1.setHaveTarget(true);
            } else if (MapTypeEnum.shu2.getValue().equals(playerMap.getTargetWith())) {
                player2.setHaveTarget(true);
            } else if (MapTypeEnum.shu3.getValue().equals(playerMap.getTargetWith())) {
                player3.setHaveTarget(true);
            } else if (MapTypeEnum.shu4.getValue().equals(playerMap.getTargetWith())) {
                player4.setHaveTarget(true);
            }
        } else {
            if (MapTypeEnum.cao1.getValue().equals(playerMap.getTargetWith())) {
                player1.setHaveTarget(true);
            } else if (MapTypeEnum.cao2.getValue().equals(playerMap.getTargetWith())) {
                player2.setHaveTarget(true);
            } else if (MapTypeEnum.cao3.getValue().equals(playerMap.getTargetWith())) {
                player3.setHaveTarget(true);
            } else if (MapTypeEnum.cao4.getValue().equals(playerMap.getTargetWith())) {
                player4.setHaveTarget(true);
            }
        }

        Constants.player1 = player1;
        Constants.player2 = player2;
        Constants.player3 = player3;
        Constants.player4 = player4;

        Constants.birthCoord = new Coord(playerMap.getSelfGraveColLen(), playerMap.getSelfGraveRowLen());


    }

    public ClientRes move(PlayerMap playerMap) {
        init(playerMap);

        if (targetIsInBirth()) {
            //    落位策略
            //一个角色在出生点守护目标
            Player goToTargetPlayer = Constants.players.stream().min(Comparator.comparingInt(player -> AStar.calcH(player.getCoord(), Constants.targetCoord)))
                    .get();
            Direct direct = AStar.getDirect(goToTargetPlayer.getCoord(), Constants.targetCoord, CanNotInEnum.NoFear);
            goToTargetPlayer.setDirect(direct);
            handleCoordByDirect(goToTargetPlayer);
            //    剩余角色落位
            //计算出3个总要点位
            winAndWaitForNextTarget();
        } else if (!Constants.haveTarget) {
            //抢夺策略
            //判断敌方是否已获得宝物
            if (!enemyHaveTarget()) {
                //判断是否在开局阶段
                if (Constants.playerMap.getSelfTargetNum() + Constants.playerMap.getOpponentTargetNum() < 1) {
                    //开局阶段尽量分散
                    Constants.players
                            .forEach(player -> {
                                Direct direct = AStar.getDirect(player.getCoord(), Constants.targetCoord, CanNotInEnum.Friend);
                                player.setDirect(direct);
                                handleCoordByDirect(player);
                            });
                } else {
                    //如果敌方还未获得宝物则所有友方单位冲向宝物
                    Constants.players
                            .forEach(player -> {
                                Direct direct = AStar.getDirect(player.getCoord(), Constants.targetCoord, CanNotInEnum.NoFear);
                                player.setDirect(direct);
                                handleCoordByDirect(player);
                            });
                }
            }
            //判断是否还有希望去攻击
            else if (canAttack(Constants.enemies)) {
                Constants.players.stream().sorted(Comparator.comparingInt(player -> AStar.calcH(Constants.targetCoord, player.getCoord())))
                        .forEach(player -> {
                            Direct direct = AStar.getDirect(player.getCoord(), Constants.targetCoord, CanNotInEnum.Friend);
                            player.setDirect(direct);
                            handleCoordByDirect(player);
                        });
            } else {
                //计算出4个总要点位
                get4ImportantCoords();
                //    如果没有希望就去等待下一个刷新
                Constants.importantCoords
                        .forEach(importantCoord -> {
                            if (haveNotOverPlayer()) {
                                Player bestPlayer = Constants.players.stream()
                                        .filter(player -> !player.getIsOver())
                                        .min(Comparator.comparingInt(player -> AStar.calcH(player.getCoord(), importantCoord)))
                                        .get();
                                Direct direct = AStar.getDirect(bestPlayer.getCoord(), importantCoord, CanNotInEnum.NoFear);
                                bestPlayer.setDirect(direct);
                                handleCoordByDirect(bestPlayer);
                            }
                        });

            }
        } else {
            //逃跑策略
            //携带宝物的兵往出生点跑
            Constants.players.stream().filter(Player::getHaveTarget)
                    .forEach(player -> {
                        Direct direct = AStar.getDirect(player.getCoord(), Constants.birthCoord, CanNotInEnum.EnemyAndNear);
                        player.setDirect(direct);
                        handleCoordByDirect(player);
                    });
            //    未携带宝物的兵的行动策略
            Constants.players.stream().filter(player -> !player.getHaveTarget())
                    .forEach(player -> {
                        if (!targetIsInBirth()) {
                            //判断敌方是否还有希望拦截
                            if (enemyCanAttack()) {
                                //敌方还有希望拦截，前去保护宝物
                                Direct direct = AStar.getDirect(player.getCoord(), Constants.targetCoord, CanNotInEnum.NoFear);
                                player.setDirect(direct);
                                handleCoordByDirect(player);
                            } else {
                                winAndWaitForNextTarget();
                            }
                        } else {
                            winAndWaitForNextTarget();
                        }
                    });

        }


        return ClientRes.builder()
                .player1(Constants.player1.getDirect())
                .player2(Constants.player2.getDirect())
                .player3(Constants.player3.getDirect())
                .player4(Constants.player4.getDirect())
                .build();


    }

    private void winAndWaitForNextTarget() {
        //计算出3个总要点位
        get3ImportantCoords();
        //宝物已到出生点，则去地图总要点位
        Constants.importantCoords.stream()
                .sorted(Comparator.comparingInt(important -> AStar.calcH((Coord) important, Constants.birthCoord)).reversed())
                .forEach(importantCoord -> {
                    if (haveNotOverPlayer()) {
                        Player bestPlayer = Constants.players.stream()
                                .filter(player1 -> !player1.getIsOver())
                                .min(Comparator.comparingInt(player1 -> AStar.calcH(player1.getCoord(), importantCoord)))
                                .get();
                        Direct direct = AStar.getDirect(bestPlayer.getCoord(), importantCoord, CanNotInEnum.NoFear);
                        bestPlayer.setDirect(direct);
                        handleCoordByDirect(bestPlayer);
                    }
                });
    }


    private void get4ImportantCoords() {
        //    设定地图中的关键点
        List<Coord> importantCoords = new ArrayList<>();
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2, Constants.playerMap.getRowLen() / 2 - 1));
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2 - 1, Constants.playerMap.getRowLen() / 2));
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2 + 1, Constants.playerMap.getRowLen() / 2));
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2, Constants.playerMap.getRowLen() / 2 + 1));
        Constants.importantCoords = importantCoords;
    }

    private void get3ImportantCoords() {
        //    设定地图中的关键点
        List<Coord> importantCoords = new ArrayList<>();
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2 - 1, Constants.playerMap.getRowLen() / 2 - 1));
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2 + 1, Constants.playerMap.getRowLen() / 2 - 1));
        importantCoords.add(new Coord(Constants.playerMap.getColLen() / 2, Constants.playerMap.getRowLen() / 2 + 1));
        Constants.importantCoords = importantCoords;
    }

    private boolean haveNotOverPlayer() {
        return Constants.players.stream().anyMatch(player -> !player.getIsOver());
    }

    //判断带有宝物的己方单位是否已到达出生点
    private boolean targetIsInBirth() {
        return Constants.players.stream()
                .anyMatch(player -> player.getCoord().equals(Constants.birthCoord))
                && Constants.targetCoord.equals(Constants.birthCoord);
    }

    private boolean enemyHaveTarget() {
        //敌方单位与宝物坐标重合或者我方未获得宝物且宝物在移动
        return Constants.enemies.stream()
                .anyMatch(enemy -> Objects.nonNull(enemy.getCoord()) && enemy.getCoord().equals(Constants.targetCoord))
                || (
                !Constants.haveTarget && !Constants.targetCoord.equals(Constants.lastTargetCoord)
        );
    }

    private boolean canAttack(List<Player> enemies) {
        //不知道敌方是否持有宝物，那就攻击
        Optional<Player> enemyHaveTargetOptional = enemies.stream().filter(Player::getHaveTarget).findAny();
        if (!enemyHaveTargetOptional.isPresent()) {
            return true;
        }

        Integer minLength = Constants.players.stream().map(player -> AStar.calcH(player.getCoord(), Constants.targetCoord))
                .min(Integer::compareTo).get();
        return minLength <= (20 - Constants.playerMap.getOpponentTargetStep());
    }

    //判断敌方是否有希望拦截
    private boolean enemyCanAttack() {
        //首先我方有宝物
        //敌方所有单位都在视野范围内
        if (Constants.haveTarget && allEnemyCanSee()) {
            //    如果回出生点路径没有敌方单位阻挡，说明敌方无法攻击
            return enemyIsHunting();

        }

        return true;
    }

    //判断是否所有敌方单位都在视野内
    private boolean allEnemyCanSee() {
        return Constants.enemies.stream().allMatch(enemy -> Objects.nonNull(enemy.getCoord()));
    }

    //如果回出生点最短路径方向与实际方向不同，说明敌方正在拦截
    private boolean enemyIsHunting() {
        if (Constants.haveTarget) {
            //最短路径方向
            Direct bestDirect = AStar.getDirect(Constants.targetCoord, Constants.birthCoord, CanNotInEnum.NoFear);
            //    实际逃离方向
            Direct realDirect = AStar.getDirect(Constants.targetCoord, Constants.birthCoord, CanNotInEnum.EnemyAndNear);
            if (bestDirect != realDirect) {
                //如果方向不同则一定会被拦截
                return true;
            }
            //    判断是否有可达路径
            if (!AStar.canArrive(Constants.targetCoord, Constants.birthCoord, CanNotInEnum.EnemyAndNear)) {
                //如果目标不可达，说明会被拦截
                return true;
            }
            return false;
        }
        return true;
    }


    private void handleCoordByDirect(Player player) {
        Coord coord = player.getCoord();
        if (player.getDirect() == Direct.DOWN) {
            coord.setY(coord.getY() + 1);
        } else if (player.getDirect() == Direct.UP) {
            coord.setY(coord.getY() - 1);
        } else if (player.getDirect() == Direct.LEFT) {
            coord.setX(coord.getX() - 1);
        } else if (player.getDirect() == Direct.RIGHT) {
            coord.setX(coord.getX() + 1);
        }
        player.setCoord(coord);
        player.setIsOver(true);
        if (player.getHaveTarget()) {
            Constants.targetCoord = coord;
        }

    }


    private Boolean haveTarget() {
        return Constants.player1.getHaveTarget() || Constants.player2.getHaveTarget() || Constants.player3.getHaveTarget() || Constants.player4.getHaveTarget();
    }

    private void init(PlayerMap playerMap) {
        Player player1 = new Player();
        Player player2 = new Player();
        Player player3 = new Player();
        Player player4 = new Player();
        Player enemy1 = new Player();
        Player enemy2 = new Player();
        Player enemy3 = new Player();
        Player enemy4 = new Player();


        if (NameEnum.SHU.getValue().equals(playerMap.getName())) {
            player1.setName(MapTypeEnum.shu1.getValue());
            player2.setName(MapTypeEnum.shu2.getValue());
            player3.setName(MapTypeEnum.shu3.getValue());
            player4.setName(MapTypeEnum.shu4.getValue());
            enemy1.setName(MapTypeEnum.cao1.getValue());
            enemy2.setName(MapTypeEnum.cao2.getValue());
            enemy3.setName(MapTypeEnum.cao3.getValue());
            enemy4.setName(MapTypeEnum.cao4.getValue());
        } else {
            player1.setName(MapTypeEnum.cao1.getValue());
            player2.setName(MapTypeEnum.cao2.getValue());
            player3.setName(MapTypeEnum.cao3.getValue());
            player4.setName(MapTypeEnum.cao4.getValue());
            enemy1.setName(MapTypeEnum.shu1.getValue());
            enemy2.setName(MapTypeEnum.shu2.getValue());
            enemy3.setName(MapTypeEnum.shu3.getValue());
            enemy4.setName(MapTypeEnum.shu4.getValue());
        }

        Coord player1Coord = MapUtil.findCoord(playerMap.getMap(), player1.getName());
        Coord player2Coord = MapUtil.findCoord(playerMap.getMap(), player2.getName());
        Coord player3Coord = MapUtil.findCoord(playerMap.getMap(), player3.getName());
        Coord player4Coord = MapUtil.findCoord(playerMap.getMap(), player4.getName());
        player1.setCoord(player1Coord);
        player2.setCoord(player2Coord);
        player3.setCoord(player3Coord);
        player4.setCoord(player4Coord);
        Coord enemy1Coord = MapUtil.findCoord(playerMap.getMap(), enemy1.getName());
        Coord enemy2Coord = MapUtil.findCoord(playerMap.getMap(), enemy2.getName());
        Coord enemy3Coord = MapUtil.findCoord(playerMap.getMap(), enemy3.getName());
        Coord enemy4Coord = MapUtil.findCoord(playerMap.getMap(), enemy4.getName());
        enemy1.setCoord(enemy1Coord);
        enemy2.setCoord(enemy2Coord);
        enemy3.setCoord(enemy3Coord);
        enemy4.setCoord(enemy4Coord);

        if (Objects.nonNull(Constants.targetCoord)) {
            Constants.lastTargetCoord = new Coord(Constants.targetCoord.getX(), Constants.targetCoord.getY());
        }
        Constants.targetCoord = MapUtil.findCoord(playerMap.getMap(), MapTypeEnum.target.getValue());

        if (NameEnum.SHU.getValue().equals(playerMap.getName())) {
            if (MapTypeEnum.shu1.getValue().equals(playerMap.getTargetWith())) {
                player1.setHaveTarget(true);
            } else if (MapTypeEnum.shu2.getValue().equals(playerMap.getTargetWith())) {
                player2.setHaveTarget(true);
            } else if (MapTypeEnum.shu3.getValue().equals(playerMap.getTargetWith())) {
                player3.setHaveTarget(true);
            } else if (MapTypeEnum.shu4.getValue().equals(playerMap.getTargetWith())) {
                player4.setHaveTarget(true);
            } else if (MapTypeEnum.cao1.getValue().equals(playerMap.getTargetWith())) {
                enemy1.setHaveTarget(true);
            } else if (MapTypeEnum.cao2.getValue().equals(playerMap.getTargetWith())) {
                enemy2.setHaveTarget(true);
            } else if (MapTypeEnum.cao3.getValue().equals(playerMap.getTargetWith())) {
                enemy3.setHaveTarget(true);
            } else if (MapTypeEnum.cao4.getValue().equals(playerMap.getTargetWith())) {
                enemy4.setHaveTarget(true);
            }
        } else {
            if (MapTypeEnum.cao1.getValue().equals(playerMap.getTargetWith())) {
                player1.setHaveTarget(true);
            } else if (MapTypeEnum.cao2.getValue().equals(playerMap.getTargetWith())) {
                player2.setHaveTarget(true);
            } else if (MapTypeEnum.cao3.getValue().equals(playerMap.getTargetWith())) {
                player3.setHaveTarget(true);
            } else if (MapTypeEnum.cao4.getValue().equals(playerMap.getTargetWith())) {
                player4.setHaveTarget(true);
            } else if (MapTypeEnum.shu1.getValue().equals(playerMap.getTargetWith())) {
                enemy1.setHaveTarget(true);
            } else if (MapTypeEnum.shu2.getValue().equals(playerMap.getTargetWith())) {
                enemy2.setHaveTarget(true);
            } else if (MapTypeEnum.shu3.getValue().equals(playerMap.getTargetWith())) {
                enemy3.setHaveTarget(true);
            } else if (MapTypeEnum.shu4.getValue().equals(playerMap.getTargetWith())) {
                enemy4.setHaveTarget(true);
            }
        }


        Constants.player1 = player1;
        Constants.player2 = player2;
        Constants.player3 = player3;
        Constants.player4 = player4;
        Constants.enemy1 = enemy1;
        Constants.enemy2 = enemy2;
        Constants.enemy3 = enemy3;
        Constants.enemy4 = enemy4;


        Constants.haveTarget = haveTarget();

        List<Player> players = new ArrayList<>();
        players.add(Constants.player1);
        players.add(Constants.player2);
        players.add(Constants.player3);
        players.add(Constants.player4);
        Constants.players = players;


        List<Player> enemies = new ArrayList<>();
        enemies.add(Constants.enemy1);
        enemies.add(Constants.enemy2);
        enemies.add(Constants.enemy3);
        enemies.add(Constants.enemy4);
        Constants.enemies = enemies;

        get4ImportantCoords();


    }
}
